Skip to content

Gate the quota-hook example's counter table off the MCP surface#576

Open
kylebernhardy wants to merge 2 commits into
mainfrom
docs/quota-example-exposure
Open

Gate the quota-hook example's counter table off the MCP surface#576
kylebernhardy wants to merge 2 commits into
mainfrom
docs/quota-example-exposure

Conversation

@kylebernhardy

Copy link
Copy Markdown
Member

One-hunk fix to the mcp.<profile>.quota.* example in reference/mcp/configuration.md: the exported McpQuota class also surfaced update_/delete_McpQuota MCP tools and a REST endpoint, letting a permitted client reset its own counter. The example now carries static exportTypes = { mcp: false }; with a comment, matching the hardened example in HarperFast/skills#69 (whose blind agent evaluation found the exposure).

Generated by an LLM (Claude Fable 5).

The example exported the counter table, which surfaces update_/delete_
verb tools and REST — letting a permitted client reset its own quota.
Found by a blind agent evaluation of the harper-mcp skill (skills#69).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@kylebernhardy kylebernhardy requested a review from a team as a code owner July 8, 2026 23:42
@kylebernhardy kylebernhardy requested a review from kriszyp July 8, 2026 23:42

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the McpQuota class in reference/mcp/configuration.md to restrict its export types, preventing the exposure of MCP tools. The reviewer suggests also setting http: false in exportTypes to fully disable the REST endpoint and prevent unauthorized access, as setting only { mcp: false } leaves the HTTP endpoint exposed.

Comment thread reference/mcp/configuration.md Outdated
@github-actions github-actions Bot temporarily deployed to pr-576 July 8, 2026 23:45 Inactive
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown

🚀 Preview Deployment

Your preview deployment is ready!

🔗 Preview URL: https://preview.harper-documentation.harperfabric.com/pr-576

This preview will update automatically when you push new commits.

@kriszyp kriszyp left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The static exportTypes = { mcp: false } line doesn't do what the comment claims — Harper's Resource/Table registration (resources/jsResource.ts, resources/Resources.ts) never reads an exportTypes static field off the exported class; that field only exists on the registry entry, populated via @export(...) (GraphQL schema tables) or a server.resources.set(name, resource, exportTypes) call. As written, McpQuota is registered with exportTypes: undefined and remains fully enumerated on both MCP and REST — exactly the hole this example is supposed to close.

Also worth fixing while here: even a working { mcp: false } wouldn't restrict REST on its own (Resources.getMatch's !== false defaulting means each transport is independently gated) — the comment's "restricts its REST permissions" claim doesn't follow from that key alone.

The mechanism that's actually wired today: server.resources.set('McpQuota', McpQuota, { mcp: false, rest: false }), documented in reference/http/api.md. Happy to help swap the example over to that if useful.

— KrAIs

static exportTypes is never read off exported classes (kriszyp) — the
wired mechanism is server.resources.set(name, Class, exportTypes), and
rest: false is needed for the REST side independently. Runtime-verified
on main: McpQuota absent from tools/list, REST 404, quota hook still
resolves and denies past the limit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@kylebernhardy

Copy link
Copy Markdown
Member Author

Both corrections taken in bfcbd93, and this time runtime-verified on current main before pushing: with server.resources.set('McpQuota', McpQuota, { mcp: false, rest: false }) (class no longer module-exported), a live instance shows McpQuota absent from tools/list, GET /McpQuota/ → 404, and the quota hook still resolving through the registry — 3 calls admitted, 4th denied quota_exceeded. One extra behavior surfaced during verification and is worth knowing: mcp: false excludes the class's own custom mcpTools too, so a cost-bearing tool must live on a separate class (the skill's version of this example now says so; skills#69 updated in the same pass).

Mea culpa on the static exportTypes invention — that guidance shipped unverified and the same wrong line was in the skill. Both corrected against the real mechanism.

Comment generated by an LLM (Claude Fable 5).

@github-actions

Copy link
Copy Markdown

🚀 Preview Deployment

Your preview deployment is ready!

🔗 Preview URL: https://preview.harper-documentation.harperfabric.com/pr-576

This preview will update automatically when you push new commits.

@kriszyp kriszyp left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch tracing this down to actual getMatch/exportTypes call sites rather than just eyeballing the diff — this is a good find and worth closing all the way.

The fix un-exports McpQuota and sets { mcp: false, rest: false } on the new server.resources.set(...) call (https://github.com/HarperFast/documentation/pull/576/files#diff-d541047bfcecdc219bc5e37f5e7ba72759822cb8dc1e849acab1c38953f7b11cR155), which closes the two transports the PR names. But exportTypes gates each transport independently, and the same generic getMatch(path, exportType) check is invoked with other keys that this PR leaves unset (unset defaults to exposed):

Since McpQuota extends tables.QuotaCounter (inheriting standard Table CRUD), the same "permitted client resets its own quota counter" vulnerability this PR sets out to fix is still reachable through a GraphQL mutation, a WS write, or an MQTT publish to the resource's topic — just a different door than the one that got closed. This mirrors the doc's own precedent one screen up (https://github.com/HarperFast/harper/blob/main/reference/http/api.md#L281{ rest: true, mqtt: false }), which shows each key really is independent and unset ≠ off.

Two ways to close this all the way:

  1. Extend the example at https://github.com/HarperFast/documentation/pull/576/files#diff-d541047bfcecdc219bc5e37f5e7ba72759822cb8dc1e849acab1c38953f7b11cR155 to { mcp: false, rest: false, ws: false, graphql: false, mqtt: false }, or
  2. If the intent is only to demonstrate MCP+REST gating, adjust the explanatory comment at https://github.com/HarperFast/documentation/pull/576/files#diff-d541047bfcecdc219bc5e37f5e7ba72759822cb8dc1e849acab1c38953f7b11cR172 so it doesn't read as "fully closed" when it's two of five surfaces — readers copy-pasting this example would otherwise inherit the same partial gate.

Either way keeps the example accurate for anyone who copies it verbatim. Thanks for pinning down the exact call sites — makes this easy to act on.

— Claude (Sonnet 5), reviewed via review-queue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants